#include "ace.h"

// returns whether a bit was successfully read [true] or
// a timeout occurred [false]
//
// bit value is returned by reference.
//
static bool readBit(byte& bit)
{
  unsigned long toc = timer0_overflow_count;

  // Wait for a rising edge. Time out if we see the overflow
  // count of timer0 increase by a significant amount. Here
  // that amount is 2 - the counter might be on the verge of
  // tipping.
  //
  while ((PINB & 1) == 0)
  {
    if (timer0_overflow_count - toc > 1)
    {
      return false;
    }
  }

  // take a sample at on-eighth of the way through a 0 bit, or 1/4 of the way
  // through a 1 bit. see diagram.
  //
  delayMicroseconds(usPerEighthCycle);
  bit = PINB & 1;

  // take second sample. xoring gives us the added bonus of being signal-phase
  // tolerant!
  //
  delayMicroseconds(usPerQuarterCycle);
  bit ^= PINB & 1;

  // a final delay to put us near the final low value part of the cycle. if you
  // routinely need more processing time/bit, then decrease this delay.
  //
  delayMicroseconds(usPerQuarterCycle);

  // it's assumed that there will be some processing occurring after this read.
  // if you find you're re-entering while a '1' bit is still in train then re-
  // enable this code. otherwise leave it out as you get slightly more processing
  // time after each read.
  //
  while((PINB & 1) == 1);

  return true;
}


// returns true if the expected bit arrives
//
static bool assertBit(byte expected)
{
  byte bit;
  return readBit(bit) && bit == expected;
}


// returns true if byte of data read, false if time out
// data byte returned as a reference.
//
static bool readByte(byte& data)
{
  byte x;
  for (int i = 0; i < 8; ++i)
  {
    if (!readBit(x))
    {
      return false;
    }

    data <<= 1;
    data |= x;
  }

  return true;
}


bool ace::detectLeadIn(unsigned long timeout)
{
  // Wait for the lead-in to start.
  //
  // This should really be ruggedised.
  //
  unsigned long exitTime = millis() + timeout;

  while ((PINB & 1) == 0)
  {
    if (millis() > exitTime)
    {
      return false;
    }
  }

  // There's the high signal.. now wait for a 1-0 transition.
  //
  while ((PINB & 1) != 0);

  // Assert that we're in the lead-in by counting 10 lead pulses.
  //
  for (int i = 0; i < 10; ++i)
  {
    if (!assertBit(LEADBIT))
    {
      return false;
    }
  }

  return true;
}



// Load data. The data arrives in 512-byte chunks, and the buffer transfer
// function is called when a block has arrived.
//
// Returns true if all went well. Return false if there was a problem -
// returnCode indicates the point of failure.
//
bool ace::loadFile(unsigned long timeout, byte* buffer, void(*bufferTransferFn)(int), int& returnCode)
{
  int dataLen = 0;

  byte bit;
  byte data;

  // failure waiting for lead-in
  //
  returnCode = 1;

  if (!detectLeadIn(timeout))
    return false;

  // failure waiting for the start bit [2]
  //
  ++returnCode;

  // Wait for a start bit then read the data length, asserting the
  // start and stop bits.
  //
  do
  {
    if (!readBit(bit))
    {
      return false;
    }
  }
  while(bit != STARTBIT);

  // failure reading data size [3]
  //
  ++returnCode;

  if (!readByte(data))
    return false;

  dataLen = data * 256;

  if (!assertBit(STOPBIT))
    return false;

  if (!assertBit(STARTBIT))
    return false;

  if (!readByte(data))
    return false;

  dataLen += data;

  if (!assertBit(STOPBIT))
    return false;


  // failure reading data [4]
  //
  ++returnCode;

  // start reading in chunks of 512 bytes.
  //
  for (int i = 0; i < dataLen; ++i)
  {
    if (assertBit(STARTBIT))
    {
      if (readByte(buffer[i & 511]))
      {
        if (assertBit(STOPBIT))
        {
          if ((dataLen & 511) == 511)
          {
            // filled the buffer, we have.
            bufferTransferFn(0);
            syncToStartBit();
          }
          continue;
        }
      }
    }

    return false;
  }

  // Yaay!
  //
  return true;
}
